3. The Ellipse and Rectangle Tools
Next up are the Ellipse
and Rectangle tools. They are extremely similar to one another, and also
to the Line tool. From a user standpoint, they function similarly: you
touch in one corner, drag, and release to define the opposite corner.
The Rectangle tool creates a rectangle, and the Ellipse tool creates
(you guessed it) an ellipse.
Make a new RectangleTool class, and give it this code:
// RectangleTool.h
#import <Foundation/Foundation.h>
#import "Tool.h"
@interface RectangleTool : NSObject <Tool> {
id <ToolDelegate> delegate;
NSMutableArray *trackingTouches;
NSMutableArray *startPoints;
}
+ (RectangleTool *)sharedRectangleTool;
@end
// RectangleTool.m
#import "RectangleTool.h"
#import "PathDrawingInfo.h"
#import "SynthesizeSingleton.h"
@implementation RectangleTool
@synthesize delegate;
SYNTHESIZE_SINGLETON_FOR_CLASS(RectangleTool);
- init {
if ((self = [super init])) {
trackingTouches = [[NSMutableArray array] retain];
startPoints = [[NSMutableArray array] retain];
}
return self;
}
- (void)activate {
}
- (void)deactivate {
[trackingTouches removeAllObjects];
[startPoints removeAllObjects];
}
As you can see, like the LineTool class, this class maintains arrays of startingPoints and trackingTouches.
The "touches" methods are where
the interesting work of this class is done. Like the Line tool, the
Rectangle tool is capable of tracking multiple simultaneous touches,
ultimately creating a new line for each of them.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UIView *touchedView = [delegate viewForUseWithTool:self];
for (UITouch *touch in [event allTouches]) {
// remember the touch, and its original start point, for future
[trackingTouches addObject:touch];
CGPoint location = [touch locationInView:touchedView];
[startPoints addObject:[NSValue valueWithCGPoint:location]];
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UIView *touchedView = [delegate viewForUseWithTool:self];
for (UITouch *touch in [event allTouches]) {
// make a rect from the start point to the current point
NSUInteger touchIndex = [trackingTouches indexOfObject:touch];
// only if we actually remember the start of this touch...
if (touchIndex != NSNotFound) {
CGPoint startPoint = [[startPoints objectAtIndex:touchIndex] CGPointValue];
CGPoint endPoint = [touch locationInView:touchedView];
CGRect rect = CGRectMake(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y - startPoint.y);
UIBezierPath *path = [UIBezierPath bezierPathWithRect:rect];
PathDrawingInfo *info = [PathDrawingInfo pathDrawingInfoWithPath:path fillColor:delegate.fillColor strokeColor:delegate.strokeColor];
[delegate addDrawable:info];
[trackingTouches removeObjectAtIndex:touchIndex];
[startPoints removeObjectAtIndex:touchIndex];
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
}
The following method draws
the current state of the rectangle while you are still dragging it
around. Only later does the object being drawn here get added to the
view's list of drawable items.
- (void)drawTemporary {
UIView *touchedView = [delegate viewForUseWithTool:self];
for (int i = 0; i<[trackingTouches count]; i++) {
UITouch *touch = [trackingTouches objectAtIndex:i];
CGPoint startPoint = [[startPoints objectAtIndex:i] CGPointValue];
CGPoint endPoint = [touch locationInView:touchedView];
CGRect rect = CGRectMake(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y - startPoint.y);
UIBezierPath *path = [UIBezierPath bezierPathWithRect:rect];
[delegate.fillColor setFill];
[path fill];
[delegate.strokeColor setStroke];
[path stroke];
}
}
- (void)dealloc {
[trackingTouches release];
[startPoints release];
self.delegate = nil;
[super dealloc];
}
@end
Now for the Ellipse tool. Its only substantial difference from the Rectangle tool is the creation of UIBezierPaths in touchesEnded:withEvent: and drawTemporary.
// EllipseTool.h
#import <Foundation/Foundation.h>
#import "Tool.h"
@interface EllipseTool : NSObject <Tool> {
id <ToolDelegate> delegate;
NSMutableArray *trackingTouches;
NSMutableArray *startPoints;
}
+ (EllipseTool *)sharedEllipseTool;
@end
// EllipseTool.m
#import "EllipseTool.h"
#import "PathDrawingInfo.h"
#import "SynthesizeSingleton.h"
@implementation EllipseTool
@synthesize delegate;
SYNTHESIZE_SINGLETON_FOR_CLASS(EllipseTool);
- init {
if ((self = [super init])) {
trackingTouches = [[NSMutableArray arrayWithCapacity:100] retain];
startPoints = [[NSMutableArray arrayWithCapacity:100] retain];
}
return self;
}
- (void)activate {
}
- (void)deactivate {
[trackingTouches removeAllObjects];
[startPoints removeAllObjects];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UIView *touchedView = [delegate viewForUseWithTool:self];
for (UITouch *touch in [event allTouches]) {
// remember the touch, and its original start point, for future
[trackingTouches addObject:touch];
CGPoint location = [touch locationInView:touchedView];
[startPoints addObject:[NSValue valueWithCGPoint:location]];
}
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UIView *touchedView = [delegate viewForUseWithTool:self];
for (UITouch *touch in [event allTouches]) {
// make an ellipse/oval from the start point to the current point
NSUInteger touchIndex = [trackingTouches indexOfObject:touch];
// only if we actually remember the start of this touch...
if (touchIndex != NSNotFound) {
CGPoint startPoint = [[startPoints objectAtIndex:touchIndex] CGPointValue];
CGPoint endPoint = [touch locationInView:touchedView];
CGRect rect = CGRectMake(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y - startPoint.y);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
PathDrawingInfo *info = [PathDrawingInfo pathDrawingInfoWithPath:path fillColor:delegate.fillColor strokeColor:delegate.strokeColor];
[delegate addDrawable:info];
[trackingTouches removeObjectAtIndex:touchIndex];
[startPoints removeObjectAtIndex:touchIndex];
}
}
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
}
- (void)drawTemporary {
UIView *touchedView = [delegate viewForUseWithTool:self];
for (int i = 0; i<[trackingTouches count]; i++) {
UITouch *touch = [trackingTouches objectAtIndex:i];
CGPoint startPoint = [[startPoints objectAtIndex:i] CGPointValue];
CGPoint endPoint = [touch locationInView:touchedView];
CGRect rect = CGRectMake(startPoint.x, startPoint.y, endPoint.x - startPoint.x, endPoint.y - startPoint.y);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:rect];
[delegate.fillColor setFill];
[path fill];
[delegate.strokeColor setStroke];
[path stroke];
}
}
- (void)dealloc {
[trackingTouches release];
[startPoints release];
self.delegate = nil;
[super dealloc];
}
@end
Here are the necessary changes to DudelViewController.m:
#import "RectangleTool.h"
#import "EllipseTool.h"
- (IBAction)touchEllipseItem:(id)sender {
self.currentTool = [EllipseTool sharedEllipseTool];
[ellipseButton setImage:[UIImage imageNamed:@"button_ellipse_selected.png"]];
}
- (IBAction)touchRectangleItem:(id)sender {
self.currentTool = [RectangleTool sharedRectangleTool];
[rectangleButton setImage:[UIImage imageNamed:@"button_rectangle_selected.png"]];
}
With those in place, the next two buttons at the bottom of the GUI should now be working. Figure 3
shows some of the kinds of shapes that can be created with these tools.
As with the previous tools, these also work with multitouch, so you
should be able to drag multiple fingers at once to create several
rectangles or ellipses simultaneously.